package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"short-link/model"
	"short-link/util"
	"strings"
	"time"
)

type Handler struct {
	h *HttpHandler
	w http.ResponseWriter
	r *http.Request
}

func NewHandler(h *HttpHandler, w http.ResponseWriter, r *http.Request) *Handler {
	return &Handler{h, w, r}
}

func (c *Handler) handle() {
	r := c.r
	url := r.URL
	util.Debug("URL %s", url)
	path := strings.TrimLeft(url.Path, "/")
	var resp interface{}
	var err error
	if path == "api/login" {
		resp, err = c.login()
	} else if path == "api/gen" {
		resp, err = c.genShortLink()
	} else if path == "api/history" {
		resp, err = c.shortLinkHistory()
	} else if path == "public/user_agreement.html" {
		c.userAgreement()
	} else {
		resp, err = c.parseShortLink()
	}
	if err != nil {
		c.response(1, nil, err.Error())
		util.Error("请求处理失败 %v", err)
		return
	}
	if resp == nil {
		return
	}
	c.response(0, resp, "")
}

func (c *Handler) login() (*model.LoginResDTO, error) {
	req := &model.LoginReqDTO{}
	err := c.getBody(req)
	if err != nil {
		return nil, err
	}

	if len(req.OpenId) == 0 {
		return nil, errors.New("参数错误[0]")
	}
	if len(req.Platform) == 0 {
		return nil, errors.New("参数错误[1]")
	}

	userInfo := util.GetUserCache(req.Platform, req.OpenId)
	now := time.Now()
	db := c.h.db
	var isCreate bool
	if userInfo == nil {
		userInfo = &model.UserInfo{}
		dbResult := db.Table("user_info").
			First(userInfo, "platform=? and open_id=?", req.Platform, req.OpenId)
		isCreate = dbResult.RowsAffected == 0
		if isCreate { // 没有记录
			userInfo.Platform = req.Platform
			userInfo.OpenId = req.OpenId
			userInfo.UnionId = req.UnionId
			userInfo.UpdatedAt = now
			userInfo.CreatedAt = now
			userInfo.LastLoginAt = now
			dbResult = db.Table("user_info").Create(userInfo)
			if dbResult.Error != nil {
				return nil, dbResult.Error
			}
		}
	}

	if !isCreate {
		userInfo.LastLoginAt = now
		dbResult := db.Table("user_info").Updates(userInfo)
		if dbResult.Error != nil {
			return nil, dbResult.Error
		}
	}

	token, err := util.GenerateToken(userInfo)
	if err != nil {
		return nil, err
	}

	util.AddUserCache(userInfo)

	return &model.LoginResDTO{
		Token: token,
	}, nil
}

func (c *Handler) genShortLink() (*model.GenResDTO, error) {
	userInfo, err := util.TokenValid(c.r)
	if err != nil || userInfo == nil {
		c.unauthorized()
		return nil, nil
	}

	req := &model.GenReqDTO{}
	err = c.getBody(req)
	if err != nil {
		return nil, err
	}

	longUrl := req.LongUrl
	if len(longUrl) == 0 {
		return nil, errors.New("参数错误[0]")
	}
	if req.ExpiredDuration <= 0 {
		return nil, errors.New("参数错误[1]")
	}
	if !(strings.HasPrefix(longUrl, "http://") ||
		strings.HasPrefix(longUrl, "https://")) {
		return nil, errors.New("参数错误[2]")
	}
	if strings.HasPrefix(longUrl, model.AppConfig.Server.Domain) {
		return nil, errors.New("参数错误[3]")
	}

	longUrlMd5 := util.GenerateMd5(longUrl)

	shortLink := util.GetShortLinkCacheWithLongUrl(longUrlMd5)
	db := c.h.db
	now := time.Now()
	expiredAt := now.Add(time.Duration(req.ExpiredDuration) * time.Minute)
	var isCreate bool
	if shortLink == nil {
		shortLink = &model.ShortLink{}
		dbResult := db.Table("short_link").
			First(shortLink, "user_info_id=? and long_url_md5=?", userInfo.Id, longUrlMd5)
		isCreate = dbResult.RowsAffected == 0
		if isCreate { // 没有记录
			shortLink.UserInfoId = userInfo.Id
			shortLink.LongUrl = longUrl
			shortLink.LongUrlMd5 = longUrlMd5

			var nextId int64
			row := db.Raw(`select nextval('short_link_id_seq')`).Row()
			err = row.Scan(&nextId)
			if err != nil {
				return nil, err
			}

			shortUrlPath := util.SaveWrapPath(util.Base62Encode(nextId))
			shortLink.ShortUrlPart = shortUrlPath
			shortLink.ShortUrl = model.AppConfig.Server.Domain + "/" + shortUrlPath
			shortLink.CreatedAt = now
			shortLink.UpdatedAt = now
			shortLink.ExpiredAt = expiredAt
			dbResult = db.Table("short_link").Create(shortLink)
			if dbResult.Error != nil {
				return nil, dbResult.Error
			}
		}
	}

	if !isCreate {
		shortLink.ExpiredAt = expiredAt
		dbResult := db.Table("short_link").Updates(shortLink)
		if dbResult.Error != nil {
			return nil, dbResult.Error
		}
	}

	return &model.GenResDTO{
		ShortUrl:  shortLink.ShortUrl,
		ExpiredAt: expiredAt,
	}, nil
}

func (c *Handler) shortLinkHistory() (*model.ShortHistoryResDTO, error) {
	userInfo, err := util.TokenValid(c.r)
	if err != nil || userInfo == nil {
		c.unauthorized()
		return nil, nil
	}

	req := &model.ShortHistoryReqDTO{}
	err = c.getBody(req)
	if err != nil {
		return nil, err
	}

	if req.Page < 1 {
		return nil, errors.New("参数错误[0]")
	}
	if req.Size < 1 {
		return nil, errors.New("参数错误[1]")
	}
	if req.Size > 20 {
		return nil, errors.New("参数错误[2]")
	}

	db := c.h.db
	list := make([]*model.ShortLink, 0)
	dbResult := db.Table("short_link").
		Order("updated_at desc").
		Limit(req.Size).Offset(req.Size*(req.Page-1)).
		Find(&list, "user_info_id=?", userInfo.Id)
	if dbResult.Error != nil {
		return nil, dbResult.Error
	}

	if len(list) == 0 {
		return &model.ShortHistoryResDTO{
			ShortHistoryReqDTO: req,
		}, nil
	}

	resultList := make([]*model.ShortLinkInfo, 0, len(list))
	for _, item := range list {
		util.AddShortLinkCache(item)
		resultList = append(resultList, &model.ShortLinkInfo{
			CreatedAt: item.CreatedAt,
			ExpiredAt: item.ExpiredAt,
			LongUrl:   item.LongUrl,
			ShortUrl:  item.ShortUrl,
			UpdatedAt: item.UpdatedAt,
		})
	}

	return &model.ShortHistoryResDTO{
		ShortHistoryReqDTO: req,
		List:               resultList,
	}, nil
}

var shortLinkRegexp = regexp.MustCompile(`^[a-zA-Z0-9]+$$`)

func (c *Handler) parseShortLink() (interface{}, error) {
	r := c.r
	shortUrlPath := strings.TrimLeft(r.URL.Path, "/")

	if len(shortUrlPath) < 6 {
		return nil, errors.New("参数错误[0]")
	}
	if !shortLinkRegexp.MatchString(shortUrlPath) {
		return nil, errors.New("参数错误[1]")
	}

	shortLink := util.GetShortLinkCacheWithShortUrl(shortUrlPath)
	if shortLink == nil {
		shortLink = &model.ShortLink{}
		db := c.h.db
		dbResult := db.Table("short_link").
			First(shortLink, "short_url_part=?", shortUrlPath)
		if dbResult.RowsAffected == 0 { // 没有记录
			return nil, errors.New("参数错误[2]")
		}
	}

	http.Redirect(c.w, r, shortLink.LongUrl, http.StatusFound)
	return nil, nil
}

func (c *Handler) response(code int, data interface{}, msg string) {
	if code == 0 && len(msg) == 0 {
		msg = "操作成功"
	}
	result := &model.CommonResDTO{
		Code: code,
		Data: data,
		Msg:  msg,
	}
	w := c.w
	w.Header().Set("Content-Type", "application/json")
	util.Debug("response: %v", result)
	if err := json.NewEncoder(w).Encode(result); err != nil {
		util.Error("写入错误[%v]", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func (c *Handler) unauthorized() {
	http.Error(c.w, "Unauthorized", http.StatusUnauthorized)
}

func (c *Handler) getBody(req interface{}) error {
	buf, err := io.ReadAll(c.r.Body)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(c.r.Body)
	if err != nil {
		return err
	}

	return json.Unmarshal(buf, req)
}

func (c *Handler) userAgreement() {
	w := c.w
	w.Header().Set("Content-Type", "text/html")
	fmt.Fprintf(w, `<html><head><meta charset="UTF-8"><title>用户协议</title></head><body><h1 style="text-align:center;">用户协议</h1><h3>1、短链接生成工具除授权HMS账号标识外，不收集其他用户敏感信息。</h3><h3>2、用户历史记录仅保留在中国境内。</h3></body></html>`)
}
